"""
Copyright 2009, Thomas Dejanovic, Jay Shurtz.
 
This is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.

This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this software; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.

This module is used to translate XML (and other supported formats) used in a 
hatch design file into python code.  This allows the user to write or import 
design information in other formats interchangeably with python.

For example:

xml_data = "<Struct name="'test_000'">
              <comment> "This is a struct" </comment>
              <sw> "rw" </sw>
              <hw> "r" </hw>
              <Register name="'register_b'">
                <comment> "This is a register" </comment>
                <bit_width> 32 </bit_width>
                <default> 0 </default>
              </Register>
            </Struct>"

h = HatchNode('h', register_a, xml=xml_data, additionalProperty='hey!')

"""

id = "$Id: hatch_format_translator.py 667 2010-07-01 00:12:17Z jayshurtz $"
# $URL: http://hatch.googlecode.com/svn/tags/taggle_release_2.2/hatch/hatch_library/hatch_format_translator.py $
# $Author: jayshurtz $
version = " ".join(id.split()[1:3])


from xml.dom.minidom import parseString

import to_hatch_structure
import hatch_xml_methods
from hatch_log import *
import hatch_node_factory
from hatch_constants import XML, NAME_ATTRIBUTE, PARENT_ATTRIBUTE
PROPERTIES_ATTRIBUTE = 'properties' # TODO - move this to hatch_constants.py ???
CHILDNODES_ATTRIBUTE = 'childNodes'


class HatchFormatTranslator(object):
    """ Translate a hatch node's xml property into additional hatch node properties & child nodes.
        May also alter the class of the hatch node containing the xml by returning a new hatch node
        to replace it, hence it is not a visitor object.
        Call as: hatchBlock = hatchFormatTranslator.translate(hatchBlock)
        This class exists so that the user can use xml directly in their hatch code / design file.
    """
    def __init__(self, xml=XML):
        """ Create a HatchFormatTranslator instance.
            'xml' is the key used to specify which value in a hatch node's properties contains XML.
        """
        self.xml = xml
        self.hatchNodeFactory = hatch_node_factory.HatchNodeFactory()   # Declare a hatch node factory for creating hatch nodes when parsing xml.

    def translate(self, hatchNode):
        """ This function is called by hatch to translate a design.
            'hatchNode' should be the top HatchNode instance in a design.
            Returns a new HatchNode instance which replaces the one which was 
            passed in as so: "hatchBlock = hatchFormatTranslator.translate(hatchBlock)"
        """
        logger.info(self.translate.func_name + "(" + hatchNodeClassAndName(hatchNode) + ")")
        newHatchNode = self.translate_function(hatchNode)
        return newHatchNode

    def translate_function(self, hatchNode):
        """ Translate any hatch node data from XML (or other formats) into python.
            'hatchNode' is a HatchNode instance which may or may not need to be translated into python.
            Returns a HatchNode instance which will be newly created only if the 
            passed in hatch node required translation.
        """
        logger.debug(str(hatchNode) + ".childNodes: \n" + ", ".join(str(childNode) for childNode in hatchNode.childNodes))
        childNodeList = [childNode for childNode in hatchNode.childNodes]   # Make a copy as we will otherwise be removing items as we iterate.
        for childNode in childNodeList:  # Recurse from bottom up to add newChildNode to newHatchNode automatically via hatchNode in hatchify_hatchNode_xml().
            newChildNode = self.translate_function(childNode)   # Could be the original childNode, or a new node.
            childNode.parent = None # Remove all the old childNodes from parent before adding a new child.
            hatchNode.childNodes.append(newChildNode)
        logger.debug(str(hatchNode) + " new childNodes: \n" + ", ".join(str(childNode) for childNode in hatchNode.childNodes))
        newHatchNode = self.hatchify_hatchNode_xml(hatchNode)
        # XXX Un-hatch all other formats here.
        return newHatchNode   # Return the 'new' hatchNode as class may have changed.

    def hatchify_hatchNode_xml(self, hatchNode):
        """ Create a new HatchNode instance based on the passed-in hatch node's original data & 'xml' property.        
            The new node will have no xml property, and should replace the original node. 
            This function is the inverse of generating xml from python. 
            'hatchNode' is a HatchNode instance with an xml property.
            Returns the new HatchNode instance
        """
        try:
            xmlString = hatchNode.properties[self.xml]   # Grab xml property from executable python.
        except KeyError, e:
            pass    # Property does not exist, skip it.
        else:
            dom = parseString(xmlString)    # Create the minidom document from the xml property.
            newHatchNode = self.hatchify_xml(hatch_xml_methods.next_xml_hatch_node_tag(dom), parentHatchNode=None)    # Create a new hatch node. Recurses down within xml.

            # TODO Test if newHatchNode != None:

            #logger.debug("xml hatchNode.info(): \n" + newHatchNode.info())
            newKeys = set(hatchNode.properties.keys()) - set(newHatchNode.properties.keys())
            logger.debug("newKeys: \n" + str(newKeys))
            for key in newKeys:
                newHatchNode.properties[key] = hatchNode.properties[key]   # Any python property that is not in the xml should be grabbed.
            childNodeList = [childNode for childNode in hatchNode.childNodes]   # Make a copy as we will otherwise be removing items as we iterate.
            for childNode in childNodeList:
                childNode.parent = None # Remove childNode from old parent before adding to new parent.
                newHatchNode.childNodes.append(childNode)   # Copy childNodes from python.
            logger.debug("newHatchNode.info(): \n" + newHatchNode.info())
            hatchNode = newHatchNode    # Return a new hatchNode if the class has changed. 
            del hatchNode.properties[self.xml]   # XXX Remove xml.
        return hatchNode

    #------------------------------------------------------------------------------
    # These functions are for un-hatching XML back into hatch nodes.

    def xml_node_to_hatch_node(self, xmlNode, parentHatchNode):
        """ Instanciate a hatch node using xml data.
            'xmlNode' is an xml.dom.minidom node instance for which 
                'get_xml_hatch_node_type()' should return 'HATCH_NODE'.
            'parentHatchNode' is the created hatchNode's parent attribute.  The 
                value from xml is effectively ignored, instead this attribute is 
                tracked manually where code recurses into child nodes.
            Returns a hatch node instance created by the global hatch node factory.
        """
        hatchNode = None
        attributes, properties = hatch_xml_methods.get_xml_hatch_node_details(xmlNode)
        logger.debug('attributes: ' + repr(attributes))
        logger.debug('properties: ' + repr(properties))
        try:
            hatchNodeName = attributes.pop(NAME_ATTRIBUTE)
        except KeyError, e:
            raise KeyError, "Hatched XML tags require a '" + NAME_ATTRIBUTE + "' attribute: " + get_node_xml(xmlNode)
        hatchNodeParent = attributes.pop(PARENT_ATTRIBUTE, None)
        del hatchNodeParent 
        hatchNodeClass = xmlNode.nodeName   # This is a string.
        hatchNode = self.hatchNodeFactory.create(hatchNodeClass, hatchNodeName)  # XXX Factory used here.
        hatchNode.parent = parentHatchNode  # Causes error if still 'None'.
        for key, value in properties.items():
            logger.debug("setting property: " + repr(key) + " = " + repr(value))
            hatchNode.properties[key] = value
        for key, value in attributes.items():
            logger.debug("setting attribute: " + repr(key) + " = " + repr(value))
            hatchNode.__setattr__(key, value)
        return hatchNode

    def hatchify_xml(self, xmlNode, parentHatchNode=None):  # Recursive.  TODO Use an iterator instead ?
        """ Create a hatch design based on data from an xml document.
            'xmlNode' is an xml.dom.minidom node instance, start recursion from 
                the top node in a structure by passing in the output of 
                hatch_xml_methods.next_xml_hatch_node_tag(xmlNode).
            'parentHatchNode' should be the parent HatchNode of any HatchNode instance
            that may be created from the passed in xml node.
            Returns the top HatchNode instance for the design.
        """
        hatchNode = None
        hatchNode = self.xml_node_to_hatch_node(xmlNode, parentHatchNode)
        logger.debug("hatchNode info(): \n" + hatchNode.info())
        for childNode in hatch_xml_methods.get_xml_hatch_child_nodes(xmlNode):
            childHatchNode = self.hatchify_xml(childNode, hatchNode) # Recurse, set parent as this node.
        return hatchNode


# Main for test
if __name__ == "__main__":

    #consoleHandler.setLevel(logging.DEBUG)

    hft = HatchFormatTranslator()

    from hatch_node import HatchNode
    #from hatch_node_physical import HatchNodePhysical
    #from hatch_struct   import Struct
    #from hatch_mux      import Mux
    #from hatch_register import Register



    if True:
        xml1 = """<?xml version="1.0" ?>
        <Struct name="'test_000'" parent="None">
            <target>
                "small apb slave"
            </target>
            <packed>
                True
            </packed>
            <hatch_leaf>
                False
            </hatch_leaf>
            <Register name="'register_a'" parent="'test_000'">
                <comment>
                    "This is a register"
                </comment>
                <bit_width>
                    32
                </bit_width>
                <default>
                    0
                </default>
                <sw>
                    "rw"
                </sw>
                <hw>
                    "r"
                </hw>
                <hatch_leaf>
                    True
                </hatch_leaf>
                <packed>
                    True
                </packed>
            </Register>
        </Struct>
        """
        g = HatchNode('g', xml=xml2)
        h = HatchNode('h', g, xml=xml1, stringProp='hey!')  # Has other childNodes & properties already.
        #h.visit(hft)   # This doesn't work because the format translator may have to return a different type of class instance.
        h = hft.translate(h)
        print h.info()


    if True:
        xml3 = """<?xml version="1.0" ?>
        <Struct name="'joker'">
            <target>
                "small apb slave"
            </target>
            <packed>
                True
            </packed>
            <hatch_leaf>
                False
            </hatch_leaf>
            <Register name="'register_a'" parent="'test_000'">
                <comment>
                    "This is a register"
                </comment>
                <bit_width>
                    32
                </bit_width>
                <default>
                    0
                </default>
                <sw>
                    "rw"
                </sw>
                <hw>
                    "r"
                </hw>
                <hatch_leaf>
                    True
                </hatch_leaf>
                <packed>
                    True
                </packed>
            </Register>
            <Register name="'register_b'" parent="'test_000'">
                <comment>
                    "This is a register"
                </comment>
                <bit_width>
                    32
                </bit_width>
                <default>
                    0
                </default>
                <sw>
                    "rw"
                </sw>
                <hw>
                    "r"
                </hw>
                <hatch_leaf>
                    True
                </hatch_leaf>
                <packed>
                    True
                </packed>
            </Register>
        </Struct>
        """
        g = HatchNode('g', xml=xml2)
        h = HatchNode('h', g, xml=xml3, stringProp='hey!')  # Test: Already has a register_b! Gets BOTH & Property checker should complain.
        h = hft.translate(h)
        print h.info()


    # Test that order of childNodes isn't skewed.
    if True:
        a = HatchNode('a')
        b = HatchNode('b', xml=xml2)
        c = HatchNode('c')

        e = HatchNode('e')
        f = HatchNode('f')
        d = HatchNode('d', e, f)

        h = HatchNode('h', a, b, c, d, xml=xml3)
        print h.info()
        h = hft.translate(h)
        print h.info()


